#!/usr/bin/python
'''Python interface to Google Gmail via IMAP.'''
'''Toward the end of the file, find routines to store message information in a private database,
   and create Google calendar entries corresponding to messages.'''
import imaplib
import getpass
import re
import sys
class Tag:
    def __init__(self, name, gmail):
        self.name = name
        self.gmail = gmail
class gIMAP:
    user = ''
    imap = None
    login_response = ('', '')
    tagList = []
    tag = None
    tag_uidvalidity = None
    
    def __init__(self, user):
        self.user = user
        self.imap = imaplib.IMAP4_SSL('imap.gmail.com')
    def login(self, user = None, passwd = None):
        if user:
            self.user = user
        if not passwd:
            passwd = getpass.getpass()
        self.login_response = self.imap.login(self.user, passwd)
    def tags(self, which = 'all'):
        """Gmail uses tags instead of mailboxes. Cache and return a list of tag names.
           Argument which = 'all'    to return all tag names (default)
                            'gmail'  to return Gmail built-in tag names
                            ''       (empty string) to return user-created tags
        """
        if not self.tagList:
            # Read tags ('mailtages') from the server.
            listed_tag = re.compile(r'\((?P<attributes>.{,}?)\) "(?P<delimiter>.)" "(?P<name>.*)"')
            r = self.imap.list()
            if r[0] == 'OK':
                for tag_listing in r[1]:
                    parsed = listed_tag.match(tag_listing)
                    if parsed.group('attributes').find(r'\Noselect') == -1:
                        # This tag is selectable, so we'll include it.
                        hierarchical_name = parsed.group('name').split(parsed.group('delimiter'))
                        name = hierarchical_name[-1]
                        # Determine whether this is a Gmail built-in tag.
                        gmail = (len(hierarchical_name) > 1 and hierarchical_name[0] == '[Gmail]')
                        self.tagList.append(Tag(name, gmail))
        # Return the cached list of tags.
        if which == 'all':
            return [tag.name for tag in self.tagList]
        elif which == '':
            return [tag.name for tag in self.tagList if not tag.gmail]
        elif which == 'gmail':
            return [tag.name for tag in self.tagList if tag.gmail]
    def tag(self, tag = 'INBOX'):
        """Select a tag to filter messages. Return the number of messages matched.
           Default tag is 'INBOX'.
        """
        r = self.imap.select(tag, readonly = True)
        if r[0] == 'OK':
            nMessages = long(r[1][0])
            self.tag = tag
            check = self.imap.status(tag, '(UIDVALIDITY)')
            if check[0] == 'OK':
                self.tag_uidvalidity = check[1][0]
            return nMessages
    def headers(self, msg_number_set):
        """Get message headers from the server.
           The msg_number_set is a string consisting of digits, commas, and colons.
           E.g., '1', '1:10', '1,4,5', '1:5,10'.
           '1:*' indicates all message numbers.
           Return a list of quartets of message number, message UID,
           internal date, and message header dictionary.
        """
        r = self.imap.fetch(msg_number_set, '(UID INTERNALDATE BODY[HEADER])')
        layout0 = re.compile(r'(?P<msgnum>\d+) \(UID (?P<uid>\d+) INTERNALDATE "(?P<date>.{,})"')
        if r[0] == 'OK':
            header_blocks = []
            # r[1] is a list of strings: header packet, ')', header packet, ')', ...
            for header_packet in r[1]:
                if header_packet == ')':
                    continue
                # Our header packet is a duple of strings.
                #   first:  'msg_number (UID uid INTERNALDATE "dd-Mon-yyyy hh:mm:ss +0000" BODY[HEADER] {length_of_header_text}'
                #   second: 'header_name: value\r\nheader_name: value\r\n... header_name: value\r\n\r\n'
                # Addendum:
                #   The string of header name:value pairs also contains header continuation lines,
                #   which start with whitespace and continue the previous line.
                parse0 = layout0.match(header_packet[0])
                msg_number = parse0.group('msgnum')
                msg_uid = parse0.group('uid')
                msg_date = parse0.group('date')
                lines = header_packet[1].splitlines()[:-1]
                headers = {}
                header = ''
                for line in lines:
                    if line.startswith((' ', "\t")):
                        # Concatenate lines.
                        header += line
                        continue
                    else:
                        # One header is assembled, or we're on the first line.
                        if header:
                            self.addHeader(headers, header)  # add to dictionary
                        header = line
                # One last header here.
                if header:
                    self.addHeader(headers, header)
                # Add this block of headers to our list.
                header_blocks += [(msg_number, msg_uid, msg_date, headers)]
        return header_blocks
    def strip0(self, string):
        "Internal utility function."
        return string.strip()
    def addHeader(self, dictionary, header):
        "Internal utility. If name already in dictionary, turn it into a list of values."
        if ':' not in header:
            print >> sys.stderr, "I can't split this header:"
            print >> sys.stderr, header
            print >> sys.stderr, "Dictionary:", "\n", dictionary
            sys.exit(1)
        name, value = map(self.strip0, header.split(':', 1))
        if name in dictionary:
            if not isinstance(dictionary[name], list):
                dictionary[name] = [dictionary[name]]
            dictionary[name] += [value]
        else:
            dictionary[name] = value

def pHeaderBlock(header_block, col_widths = [25, -1]):
    # Print the standalone values.
    print 'message number', header_block[0]
    print 'UID', header_block[1]
    print 'mailserver internal date', header_block[2]
    header_dict = header_block[3]
    # Compute a header line format.
    for w in range(0, 2):
        if col_widths[w]:
            s_width[w] = str(col_widths[w])

        else:
            s_width[w] = ''
    width1 = 25
    format1 = "%-" + str(width1) + "s: "
    if limit:
        width2 = limit - width1 + 2
    else:
        width2 = '*'
    width2_str = ''
    if width2:
        width2_str = '-' + str(width2)
    format = format1 + "%" + width2_str + "s"
    # Process the dictionary.
    for key in header_dict.keys():
        print key
        if isinstance(header_dict[key], list):
            for a_header in header_dict[key]:
                print format % (key, a_header[:width2])
        else:
            print format % (key, header_dict[key][:width2])

import sqlite3
class MsgDB:
    '''Store messages in SQLite. Provide a file name to the constructor.'''
    db = None
    cursor = None
    def __init__(self, db_name):
        # Connect to the named file, with autocommit.
        self.db = sqlite3.connect(db_name, isolation_level = None)
        self.cursor = self.db.cursor()
    def mktables(self):
        self.cursor.execute('''create table msg
            (uid integer, internaldate text)''')
        self.cursor.execute('''create table hdr
            (uid integer, name text, value text)''')
        self.db.commit()
    def insert_msg(self, uid, internaldate):
        self.cursor.execute('insert into msg values (?, ?)', (uid, internaldate))
        self.db.commit()
    def insert_hdr(self, uid, name, value):
        self.cursor.execute('insert into hdr values (?, ?, ?)', (uid, name, value))
    def insert_hdrs(self, uid, dictionary):
        for name in dictionary.keys():
            if isinstance(dictionary[name], list):
                for value in dictionary[name]:
                    self.insert_hdr(uid, name, value)
            else:
                self.insert_hdr(uid, name, dictionary[name])
        self.db.commit()
    def insert_hdr_blocks(self, hdr_blocks):
        for block in hdr_blocks:
            print >> sys.stderr, block[0],
            self.insert_msg(block[1], block[2])
            self.insert_hdrs(block[1], block[3])
    def close(self):
        self.db.close()

# Downloading useful sent mail:
# gm.tag('[Gmail]/Send Mail')
# search for messages From dolsson@collaborative.org
# and not To davidolsson@yahoo.com.

import time
def bunch(n, imap, db, bunch_size = 100):
    '''Insert a bunch of header blocks into the database.
       Read the headers from the imap server, where a tag/mailbox is already selected.
       Write them to the MsgDB database db.
    '''
    msg_set = str((n - 1) * bunch_size + 1) + ':' + str(n * bunch_size)
    print 'bunch', n, msg_set
    print 'starting', time.asctime()
    headers = imap.headers(msg_set)
    print 'inserting',
    db.insert_hdr_blocks(headers)
    print "\ndone", time.asctime(), "\n"

def bunches(imap, db, bunch_size, first, last, delay = 30):
    for n in range(first, last + 1):
        if n != first:
            time.sleep(delay)
        bunch(n, imap, db, bunch_size)

def datestr2iso(datestr):
    months = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec']
    datefmt = re.compile('(\d+)-(...)-(\d+) (\d+):(\d+):(\d+)')
    parse = datefmt.match(datestr)
    # return [parse.group(x) for x in range(1, 7)]
    iso = parse.group(3) + '-' + format(months.index(parse.group(2)) + 1,'02d') + '-' + parse.group(1) + 'T' + parse.group(4) + ':' + parse.group(5) + ':' + parse.group(6) + '.000Z'
    return iso

import gcalpy
def all(db, gc):
    db.cursor.execute('select uid, internaldate from msg')
    f = db.cursor.fetchall()
    for msg in f:
        uid = msg[0]
        datestr = msg[1]
        print uid
        print datestr
        db.cursor.execute('select value from hdr where hdr.uid = ? and name = "To" and value <> "davidolsson@yahoo.com"', (uid,))
        fTo = db.cursor.fetchone()
        if fTo:
            to = fTo[0]
            print to
            db.cursor.execute('select value from hdr where hdr.uid = ? and name = "Subject" and value <> "davidolsson@yahoo.com"', (uid,))
            fSubject = db.cursor.fetchone()
            if fSubject:
                subject = fSubject[0]
                print subject
                ev = gcalpy.insertEvent(gc, subject + ' (' + to + ')', '', '', datestr2iso(datestr), datestr2iso(datestr))
                if not ev:
                    break




    #db.cursor.execute('select internaldate, hdr.name, hdr.value from msg left join hdr on msg.uid = hdr.uid where (hdr.name = "To" or hdr.name = "Subject") and hdr.value <> "davidolsson@yahoo.com" order by msg.uid')

# note: use UIDs to track messages, not message numbers
#       they remain attached to the message, relative to a mailbox,
#       at least for the session
# imap.authenticate(mechanism, authobject)
# imap.login(user, password)
# imap.list([directory[, pattern]])
# imap.select([mailbox[, readonly]])
# imap.search(charset, criterion[, ...])
# imap.thread(threading_algorithm, charset, search_criterion[, ...])
# e.g. typ, msgnums = imap.search(None, 'FROM', '"LDJ"')
# imap.fetch(message_set, message_parts)
# e.g. ('1-*', "(UID BODY[TEXT])")
# imap.uid(command, arg[, ...])
# imap.close()
# imap.logout()
# Complete example, without error checking:
# imap = imaplib.IMAP4_SSL('imap.gmail.com')
# imap.login(getpass.getuser(), getpass.getpass())
# imap.select()
# typ, data = M.search(None, 'ALL')
# for num in data[0].split():
#     typ, data = M.fetch(num, '(RFC822)')
#     print 'Message %s\n%s\n' % (num, data[0][1])
# imap.close()
# imap.logout()
